NOTE: This script is for learning purposes only and does not constitute a recommendation for buying or selling any stock mentioned in this script.
SUMMARY: This project aims to construct and test an algorithmic trading model and document the end-to-end steps using a template.
INTRODUCTION: This algorithmic trading model examines a simple mean-reversion strategy for a stock. The model enters a position when the price reaches either the upper or lower Relative Strength Indicator thresholds for the last X number of days. The model will exit the trade when the stock price crosses the upper or the lower RSI line for the same window size.
In iteration Take1, we set up the models using an RSI window size for long trades only. The window size varied from 10 to 50 trading days at a 5-day increment.
In iteration Take2, we set up the models using an RSI window size for long and short trades. The window size varied from 10 to 50 trading days at a 5-day increment.
In this Take3 iteration, we will set up the models using an RSI window size for long trades only. The window size will vary from 10 to 50 trading days at a 5-day increment. In addition, we will vary the upper and lower RSI thresholds to examine their effects on the return.
ANALYSIS: In iteration Take1, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 143.51 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
In iteration Take2, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 51.19 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
In this Take3 iteration, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 143.51 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
CONCLUSION: For the stock of COST during the modeling time frame, the long-only with variable RSI thresholds trading strategy did not produce a better return than the buy-and-hold approach. We should consider modeling this stock further by experimenting with more variations of the strategy.
Dataset ML Model: Time series analysis with numerical attributes
Dataset Used: Quandl
An algorithmic trading modeling project generally can be broken down into about five major tasks:
# # Install the necessary packages for Colab
# !pip install python-dotenv PyMySQL
# # Retrieve the GPU information from Colab
# gpu_info = !nvidia-smi
# gpu_info = '\n'.join(gpu_info)
# if gpu_info.find('failed') >= 0:
# print('Select the Runtime → "Change runtime type" menu to enable a GPU accelerator, ')
# print('and then re-execute this cell.')
# else:
# print(gpu_info)
# # Retrieve the memory configuration from Colab
# from psutil import virtual_memory
# ram_gb = virtual_memory().total / 1e9
# print('Your runtime has {:.1f} gigabytes of available RAM\n'.format(ram_gb))
#
# if ram_gb < 20:
# print('To enable a high-RAM runtime, select the Runtime → "Change runtime type"')
# print('menu, and then select High-RAM in the Runtime shape dropdown. Then, ')
# print('re-execute this cell.')
# else:
# print('You are using a high-RAM runtime!')
# # Retrieve the CPU information
# ncpu = !nproc
# print("The number of available CPUs is:", ncpu[0])
import pandas as pd
import matplotlib.pyplot as plt
import os
import sys
from datetime import date, datetime, timedelta
import requests
import json
from dotenv import load_dotenv
import statistics as stats
# Begin the timer for the script processing
startTimeScript = datetime.now()
# Specify the key modeling parameters below
STOCK_SYMBOL = 'COST'
INITIAL_CAPITAL = 0
# Specify the moving average parameters for the trading strategy
WINDOW_MIN = 10
WINDOW_MAX = 50
WINDOW_INCREMENT = 5
VOL_MA_MIN = 10
VOL_MA_MAX = 10
VOL_MA_INCREMENT = 10
HOLDING_MIN = 999
HOLDING_MAX = 999
HOLDING_INCREMENT = 10
RSI_HIGH = 70
RSI_MIDDLE = 50
RSI_LOW = 30
RSI_SIZE = 20
RSI_INCREMENT=5
GAIN_MAX = 0.99
LOSS_MAX = 0.99
MEAN_REVERSION = True
TREND_FOLLOWING = False
if MEAN_REVERSION and TREND_FOLLOWING: sys.exit("Cannot have both MEAN_REVERSION and TREND_FOLLOWING flags be set to True. Script Processing Aborted!!!")
LONG_ONLY = True
SHORT_ONLY = False
if LONG_ONLY and SHORT_ONLY: sys.exit("Cannot have both LONG_ONLY and SHORT_LONG flags be set to True. Script Processing Aborted!!!")
# The number of extra days of data we need for calculating moving averages (usually equals to the largest value of slow MA)
EXTRA_DAYS = WINDOW_MAX
# EXTRA_DAYS = SLOW_MA_MAX
MODEL_START_DATE = date(2016, 1, 1)
print("Starting date for the model:", MODEL_START_DATE)
# MODEL_END_DATE = datetime.now().date()
MODEL_END_DATE = date(2021, 4, 23)
print("Ending date for the model:", MODEL_END_DATE)
# data_start_date = MODEL_START_DATE
data_start_date = MODEL_START_DATE - timedelta(days=int(EXTRA_DAYS * 2)) # Need more pricing data to calculate moving averages
print("First date of data we need for modeling:", data_start_date)
data_end_date = MODEL_END_DATE
print("Last date of data we need for modeling:", data_end_date)
Starting date for the model: 2016-01-01 Ending date for the model: 2021-04-23 First date of data we need for modeling: 2015-09-23 Last date of data we need for modeling: 2021-04-23
# Specify the script running parameters below
# Set Pandas options
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)
pd.set_option("display.width", 384)
# Configure the plotting style
plt.style.use('seaborn')
# Set up the verbose flag to print detailed messages for debugging (setting True will activate!)
verbose_signals = False
verbose_models = False
verbose_graphs = True
verbose_trade_actions = False
verbose_portfolios = False
verbose_transactions = False
verbose_positions = False
# Set up the parent directory location for loading the dotenv files
# Mount Google Drive locally for storing files
# from google.colab import drive
# drive.mount('/content/gdrive')
# gdrivePrefix = '/content/gdrive/My Drive/Colab_Downloads/'
# env_path = '/content/gdrive/My Drive/Colab Notebooks/'
# dotenv_path = env_path + "python_script.env"
# load_dotenv(dotenv_path=dotenv_path)
# Set up access to the dotenv file on local PC
env_path = "/Users/david/PycharmProjects/"
dotenv_path = env_path + "python_script.env"
load_dotenv(dotenv_path=dotenv_path)
True
# Set up the data service provider and data acquisition parameters
data_service = 'Quandl'
# Check and see whether the API key is available
api_key = os.environ.get('QUANDL_API')
if api_key is None: sys.exit("API key for Quandl not available. Script Processing Aborted!!!")
start_date_string = data_start_date.strftime('%Y-%m-%d')
end_date_string = data_end_date.strftime('%Y-%m-%d')
api_url = "https://www.quandl.com/api/v3/datatables/SHARADAR/SEP.json?date.gte=%s&date.lte=%s&ticker=%s&api_key=%s&qopts.data_version=2" % (start_date_string, end_date_string, STOCK_SYMBOL, api_key)
response = requests.get(api_url)
resp_dict = json.loads(response.text)
stock_rawdata = pd.DataFrame(resp_dict['datatable']['data'])
print(len(stock_rawdata), 'data points retrieved from the API call.')
1406 data points retrieved from the API call.
stock_rawdata.columns = ['ticker', 'date', 'open', 'high', 'low', 'close', 'volume', 'closeadj', 'closeunadj', 'lastupdated']
# stock_rawdata.set_index('date', inplace=True)
stock_rawdata.index = pd.to_datetime(stock_rawdata.date)
stock_pricing = stock_rawdata.sort_index(ascending=True)
print(stock_pricing.head())
print()
print(stock_pricing.tail())
ticker date open high low close volume closeadj closeunadj lastupdated
date
2015-09-23 COST 2015-09-23 143.46 145.630 142.65 145.43 2310212.0 128.808 145.43 2021-02-04
2015-09-24 COST 2015-09-24 144.37 145.420 143.65 144.87 1974868.0 128.312 144.87 2021-02-04
2015-09-25 COST 2015-09-25 145.95 146.900 145.01 145.55 1845691.0 128.914 145.55 2021-02-04
2015-09-28 COST 2015-09-28 145.39 145.800 143.29 143.55 2506451.0 127.143 143.55 2021-02-04
2015-09-29 COST 2015-09-29 143.04 144.135 142.25 143.72 2513094.0 127.294 143.72 2021-02-04
ticker date open high low close volume closeadj closeunadj lastupdated
date
2021-04-19 COST 2021-04-19 371.00 371.530 368.20 369.55 1560002.0 369.55 369.55 2021-04-19
2021-04-20 COST 2021-04-20 369.00 375.355 368.73 371.73 2330659.0 371.73 371.73 2021-04-20
2021-04-21 COST 2021-04-21 371.61 374.580 371.47 374.09 1532098.0 374.09 374.09 2021-04-21
2021-04-22 COST 2021-04-22 374.34 375.440 370.03 371.26 2138003.0 371.26 371.26 2021-04-22
2021-04-23 COST 2021-04-23 371.26 374.850 370.41 373.28 1404929.0 373.28 373.28 2021-04-23
# Set up the standard column name for modeling
# Column names may be data-provider specific!
MODEL_TEMPLATE = stock_pricing.loc[:, ['open','closeadj','volume']]
MODEL_TEMPLATE.rename(columns={'open': 'open_price', 'closeadj': 'close_price', 'volume': 'trading_volume'}, inplace=True)
plot_title = 'Historical Stock Close Price for ' + STOCK_SYMBOL + ' from ' + data_service
MODEL_TEMPLATE['close_price'].plot(figsize=(16,9), title=plot_title)
plt.show()
# Define the function that will generate the indicators and trading signals
# General logic for processing the trading signals for each time period
# 1 - Check to see whether we need to execute a trading action from the previous day's signal. Once the trading action is executed, move on to the next day.
# 2 - If no trade actions to execute on open, check to see whether we have any breakout that generates a trading signal. If we have a new trading signal and currently has no position, mark the entry action for the next day.
# 3 - If no new signal for today, check to see whether we need to exit any existing position. If we have an exit signal and currently hold a position, mark the exit action for the next day.
# 4 - If nothing is going on, mark up the trading model appropriately and move to the next day.
def populate_signals(window=WINDOW_MIN, vol_ma=VOL_MA_MIN, max_holding=HOLDING_MAX, RSI_size=RSI_SIZE):
trade_model = MODEL_TEMPLATE.copy()
trade_model['buy_on_open'] = False
trade_model['sell_on_open'] = False
trade_model['short_on_open'] = False
trade_model['cover_on_open'] = False
trade_model['holding_period'] = None
trade_model['cost_basis'] = None
trade_model['pandl_pct'] = None
trade_model['position_long'] = None
trade_model['position_short'] = None
trade_model['volume_ma'] = trade_model['trading_volume'].rolling(vol_ma).mean()
trade_model['rsi_value'] = 0
# Calculate the RSI ratings
gain_history = []
loss_history = []
for k in range(len(trade_model)):
close_price = trade_model.at[trade_model.index[k],'close_price']
if k == 0:
trade_model.at[trade_model.index[k],'rsi_value'] = 50
else:
previous_price = trade_model.at[trade_model.index[k-1],'close_price']
gain_history.append(max(0, close_price - previous_price))
loss_history.append(max(0, previous_price - close_price))
if len(gain_history) > window: # maximum observations is equal to lookback period
del (gain_history[0])
del (loss_history[0])
avg_gain = stats.mean(gain_history) # average gain over lookback period
avg_loss = stats.mean(loss_history) # average loss over lookback period
rs = 1
if avg_loss > 0: # to avoid division by 0, which is undefined
rs = avg_gain / avg_loss
trade_model.at[trade_model.index[k],'rsi_value'] = 100 - (100 / (1 + rs))
# Truncate the model to the required starting and ending dates
trade_model = trade_model[MODEL_START_DATE:MODEL_END_DATE]
last_index = len(trade_model) - 1
RSI_low_threshold = RSI_MIDDLE - RSI_size
RSI_high_threshold = RSI_MIDDLE + RSI_size
for k in range(len(trade_model)):
# Calculate the magnitude of breakout. Positive value means breaking out towards a direction
price_today = trade_model.at[trade_model.index[k],'close_price']
rsi_value = trade_model.at[trade_model.index[k],'rsi_value']
if MEAN_REVERSION:
breakout_long_side = rsi_value < RSI_low_threshold
breakout_short_side = rsi_value > RSI_high_threshold
else:
breakout_long_side = rsi_value > RSI_high_threshold
breakout_short_side = rsi_value < RSI_low_threshold
volume_today = trade_model.at[trade_model.index[k],'trading_volume']
volume_avg = trade_model.at[trade_model.index[k],'volume_ma']
close_above_threshold = rsi_value > RSI_high_threshold
close_below_threshold = rsi_value < RSI_low_threshold
if k == 0:
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0
else:
currently_long = trade_model.at[trade_model.index[k-1],'position_long']
currently_short = trade_model.at[trade_model.index[k-1],'position_short']
holding_period = trade_model.at[trade_model.index[k-1],'holding_period']
cost_basis = trade_model.at[trade_model.index[k-1],'cost_basis']
trade_executed_today = False
# Check to see whether we need to execute any trade action on open
if trade_model.at[trade_model.index[k],'buy_on_open']:
trade_executed_today = True
currently_long = True
currently_short = False
holding_period = 1
cost_basis = -trade_model.at[trade_model.index[k],'open_price']
elif trade_model.at[trade_model.index[k],'short_on_open']:
trade_executed_today = True
currently_long = False
currently_short = True
holding_period = 1
cost_basis = trade_model.at[trade_model.index[k],'open_price']
elif trade_model.at[trade_model.index[k],'sell_on_open']:
trade_executed_today = True
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0.0
elif trade_model.at[trade_model.index[k],'cover_on_open']:
trade_executed_today = True
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0.0
else:
# If no trade on open, check to see whether we have a breakout with an entry the next day
if breakout_long_side and (not currently_long) and (not currently_short) and (volume_today > volume_avg) and (k < last_index-1) and (not SHORT_ONLY):
trade_model.at[trade_model.index[k+1],'buy_on_open'] = True
elif breakout_short_side and (not currently_long) and (not currently_short) and (volume_today > volume_avg) and (k < last_index-1) and (not LONG_ONLY):
trade_model.at[trade_model.index[k+1],'short_on_open'] = True
else:
# If no breakout, check to see whether we need to exit an existing position the next day
if currently_short and (k < last_index-1):
if (MEAN_REVERSION and close_below_threshold) or (TREND_FOLLOWING and close_above_threshold) or (holding_period+1 >= max_holding):
trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
elif currently_long and (k < last_index-1):
if (MEAN_REVERSION and close_above_threshold) or (TREND_FOLLOWING and close_below_threshold) or (holding_period+1 >= max_holding):
trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
# If no action on a given day, carry over the position status
if (k > 0) and (not trade_executed_today):
if currently_long or currently_short:
holding_period = holding_period + 1
trade_model.at[trade_model.index[k],'position_long'] = currently_long
trade_model.at[trade_model.index[k],'position_short'] = currently_short
trade_model.at[trade_model.index[k],'holding_period'] = holding_period
trade_model.at[trade_model.index[k],'cost_basis'] = cost_basis
# Check to see whether the profit or loss target has been met for exiting the position
if currently_long :
pandl_pct = (cost_basis + price_today) / abs(cost_basis)
trade_model.at[trade_model.index[k],'pandl_pct'] = pandl_pct
if (pandl_pct >= GAIN_MAX) or (pandl_pct <= -LOSS_MAX) :
trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
elif currently_short :
pandl_pct = (cost_basis - price_today) / abs(cost_basis)
trade_model.at[trade_model.index[k],'pandl_pct'] = pandl_pct
if (pandl_pct >= GAIN_MAX) or (pandl_pct <= -LOSS_MAX) :
trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
else:
trade_model.at[trade_model.index[k],'pandl_pct'] = 0.0
# # Exiting the position on the last day of modeling period
# if k == last_index-1:
# if trade_model.at[trade_model.index[k],'position_long']:
# trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
# trade_model.at[trade_model.index[k+1],'position_long'] = False
# elif trade_model.at[trade_model.index[k],'position_short']:
# trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
# trade_model.at[trade_model.index[k+1],'position_short'] = False
if verbose_signals: print(trade_model, '\n')
return trade_model
# Build the collection of trading models by iterating through the parameters
trading_model_collection = {}
serial_no = 0
for window_size in range(WINDOW_MIN, WINDOW_MAX+1, WINDOW_INCREMENT):
for vol_average in range(VOL_MA_MIN, VOL_MA_MAX+1, VOL_MA_INCREMENT):
for hold_period in range(HOLDING_MIN, HOLDING_MAX+1, HOLDING_INCREMENT):
for rsi_size in range(10, 26, RSI_INCREMENT):
serial_no += 1
model_tag = 'Model_' + str(serial_no).zfill(3) + '_WINDOW_' + str(window_size).zfill(3) + '_VOLMA_' + str(vol_average).zfill(3) + '_HOLD_' + str(hold_period).zfill(3) \
+ '_RSI-HIGH_' + str(RSI_MIDDLE+rsi_size) + '_RSI-LOW_' + str(RSI_MIDDLE-rsi_size)
if verbose_signals: print('Processing model:', model_tag)
trading_model = populate_signals(window_size, vol_average, hold_period)
trading_model_collection[model_tag] = trading_model.copy()
print(len(trading_model_collection), 'trading models generated!')
36 trading models generated!
# List the entry/exit points for each model
def list_model_entry_exit(trade_model):
print(trade_model[trade_model['buy_on_open'] | trade_model['sell_on_open'] | trade_model['short_on_open'] | trade_model['cover_on_open']])
if verbose_models:
for model_name in trading_model_collection:
print('List the signal changes and entry/exit points for model:', model_name)
list_model_entry_exit(trading_model_collection[model_name])
print()
def draw_model_graph(trade_model, mdl_name=STOCK_SYMBOL):
graph_data = trade_model.copy()
title_string = 'Simple Mean-Reversion Trading Model for ' + mdl_name
fig = plt.figure(figsize=(16,18))
ylabel_1 = STOCK_SYMBOL + ' price in $'
ax1 = fig.add_subplot(211, ylabel=ylabel_1, title=title_string)
graph_data['close_price'].plot(ax=ax1, color='g')
ax1.plot(graph_data.loc[graph_data['buy_on_open']].index, graph_data.close_price[graph_data['buy_on_open']], '^', markersize=7, color='b',label='Buy on Open')
ax1.plot(graph_data.loc[graph_data['sell_on_open']].index, graph_data.close_price[graph_data['sell_on_open']], 'v', markersize=7, color='b',label='Sell on Open')
ax1.plot(graph_data.loc[graph_data['short_on_open']].index, graph_data.close_price[graph_data['short_on_open']], '^', markersize=7, color='r',label='Short on Open')
ax1.plot(graph_data.loc[graph_data['cover_on_open']].index, graph_data.close_price[graph_data['cover_on_open']], 'v', markersize=7, color='r',label='Cover on Open')
plt.legend(loc='upper left')
ylabel_2 = STOCK_SYMBOL + ' RSI'
ax2 = fig.add_subplot(212, ylabel=ylabel_2, title=title_string)
graph_data['rsi_value'].plot(ax=ax2, color='b')
plt.show()
if verbose_graphs:
for model_name in trading_model_collection:
draw_model_graph(trading_model_collection[model_name], model_name)
def generate_trading_portfolios(trade_model):
# Construct a portfolio to track the transactions and returns
portfolio = pd.DataFrame(index=trade_model.index, columns=['trade_action', 'price_executed', 'qty_transacted', 'cost_basis', 'gain_loss', 'qty_on_hand', 'cash_on_hand', 'position_value', 'total_position', 'accum_return'])
portfolio['trade_action'] = False
portfolio.at[portfolio.index[0],'price_executed'] = 0.00
portfolio.at[portfolio.index[0],'qty_transacted'] = 0
portfolio.at[portfolio.index[0],'cost_basis'] = 0.00
portfolio.at[portfolio.index[0],'gain_loss'] = 0.00
portfolio.at[portfolio.index[0],'qty_on_hand'] = 0
portfolio.at[portfolio.index[0],'cash_on_hand'] = INITIAL_CAPITAL
portfolio.at[portfolio.index[0],'position_value'] = 0.00
portfolio.at[portfolio.index[0],'total_position'] = INITIAL_CAPITAL
portfolio.at[portfolio.index[0],'accum_return'] = 0.00
quantity = 1
# The conditional parameters below determine how the trading strategy will be carried out
for i in range(1, len(portfolio)):
price_per_share = trade_model.at[trade_model.index[i],'open_price']
if trade_model.at[trade_model.index[i],'buy_on_open']:
# Code block for Buy on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = quantity
recent_cost = price_per_share * -quantity
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] + quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + recent_cost
if verbose_trade_actions: print('BOUGHT QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', price_per_share)
elif trade_model.at[trade_model.index[i],'sell_on_open']:
# Code block for Sell on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = -quantity
recent_cost = 0.00
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = (price_per_share * quantity) + portfolio.iloc[i-1]['cost_basis']
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] - quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + (price_per_share * quantity)
if verbose_trade_actions: print('SOLD QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', price_per_share)
elif trade_model.at[trade_model.index[i],'short_on_open']:
# Code block for Short on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = -quantity
recent_cost = price_per_share * quantity
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] - quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + recent_cost
if verbose_trade_actions: print('SHORTED QTY:', -quantity, 'on', portfolio.index[i].date(), 'at the price of', trade_model.at[portfolio.index[i],'open_price'])
elif trade_model.at[trade_model.index[i],'cover_on_open']:
# Code block for Cover on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = quantity
recent_cost = 0.00
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = portfolio.iloc[i-1]['cost_basis'] - (price_per_share * quantity)
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] + quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] - (price_per_share * quantity)
if verbose_trade_actions: print('COVERED QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', trade_model.at[portfolio.index[i],'open_price'])
else:
# Code block for no trade actions
portfolio.at[portfolio.index[i],'price_executed'] = 0.00
portfolio.at[portfolio.index[i],'qty_transacted'] = 0
portfolio.at[portfolio.index[i],'cost_basis'] = portfolio.iloc[i-1]['cost_basis']
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand']
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand']
portfolio.at[portfolio.index[i],'position_value'] = trade_model.at[trade_model.index[i],'close_price'] * portfolio.at[portfolio.index[i],'qty_on_hand']
portfolio.at[portfolio.index[i],'total_position'] = portfolio.at[portfolio.index[i],'cash_on_hand'] + portfolio.at[portfolio.index[i],'position_value']
portfolio.at[portfolio.index[i],'accum_return'] = portfolio.at[portfolio.index[i],'total_position'] - INITIAL_CAPITAL
if verbose_portfolios: print('\n', portfolio, '\n')
return portfolio
def calculate_positions_and_performance(trade_model):
trade_positions = generate_trading_portfolios(trade_model)
trade_transactions = trade_positions[trade_positions['trade_action']]
if verbose_transactions: print(trade_transactions)
if trade_transactions.at[trade_transactions.index[-1],'trade_action']:
if trade_transactions.at[trade_transactions.index[-1],'qty_on_hand'] == 0:
print('The current status of the model is:','Waiting to enter a position since',trade_transactions.index.tolist()[-1].date(),'\n')
elif trade_transactions.at[trade_transactions.index[-1],'qty_on_hand'] > 0:
print('The current status of the model is:','Holding a long position since',trade_transactions.index.tolist()[-1].date(),'\n')
else:
print('The current status of the model is:','Holding a short position since',trade_transactions.index.tolist()[-1].date(),'\n')
return trade_positions
# Convert trading models into positions and calculate profit and loss
# Initialize a dictionary for tracking positions for all models
model_positions_collection={}
for model_name in trading_model_collection:
print('Processing the positions for model:', model_name)
model_positions_collection[model_name] = calculate_positions_and_performance(trading_model_collection[model_name])
print(len(model_positions_collection), 'sets of model positions generated.')
Processing the positions for model: Model_001_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2021-03-23 Processing the positions for model: Model_002_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2021-03-23 Processing the positions for model: Model_003_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2021-03-23 Processing the positions for model: Model_004_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2021-03-23 Processing the positions for model: Model_005_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2021-03-29 Processing the positions for model: Model_006_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2021-03-29 Processing the positions for model: Model_007_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2021-03-29 Processing the positions for model: Model_008_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2021-03-29 Processing the positions for model: Model_009_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2021-04-06 Processing the positions for model: Model_010_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2021-04-06 Processing the positions for model: Model_011_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2021-04-06 Processing the positions for model: Model_012_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2021-04-06 Processing the positions for model: Model_013_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2021-04-12 Processing the positions for model: Model_014_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2021-04-12 Processing the positions for model: Model_015_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2021-04-12 Processing the positions for model: Model_016_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2021-04-12 Processing the positions for model: Model_017_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2021-04-19 Processing the positions for model: Model_018_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2021-04-19 Processing the positions for model: Model_019_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2021-04-19 Processing the positions for model: Model_020_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2021-04-19 Processing the positions for model: Model_021_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_022_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_023_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_024_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_025_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_026_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_027_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_028_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_029_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_030_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_031_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_032_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_033_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2017-02-16 Processing the positions for model: Model_034_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2017-02-16 Processing the positions for model: Model_035_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2017-02-16 Processing the positions for model: Model_036_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2017-02-16 36 sets of model positions generated.
# Initialize a dataframe for storing the model's profit and loss
model_performance_summary = pd.DataFrame(columns=['Model_name','Return_value','Return_percentage'])
for model_name in model_positions_collection:
if verbose_positions: print('Processing positions for model:', model_name)
if verbose_positions: print('Accumulated profit/loss for one share of stock with initial capital of $%.0f at the end of modeling period: $%.2f' % (INITIAL_CAPITAL, model_positions_collection[model_name].accum_return[-1]))
if INITIAL_CAPITAL != 0:
return_percentage = model_positions_collection[model_name].accum_return[-1] / INITIAL_CAPITAL * 100
if verbose_positions: print('Accumulated return percentage based on the initial capital investment: %.2f%%' % return_percentage)
else:
return_percentage = None
if verbose_positions: print()
model_performance_summary = model_performance_summary.append({'Model_name': model_name, 'Return_value': model_positions_collection[model_name].accum_return[-1], 'Return_percentage': return_percentage}, ignore_index=True)
model_performance_summary.sort_values(by=['Return_value'], inplace=True, ascending=False)
print(len(model_performance_summary), 'profit/loss summaries generated.\n')
print('The top ten model\'s performance summary:')
print(model_performance_summary.head(10))
36 profit/loss summaries generated.
The top ten model's performance summary:
Model_name Return_value Return_percentage
0 Model_001_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 143.518 None
2 Model_004_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 143.518 None
3 Model_002_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 143.518 None
1 Model_003_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 143.518 None
4 Model_018_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 122.731 None
5 Model_020_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 122.731 None
6 Model_019_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 122.731 None
7 Model_017_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 122.731 None
10 Model_025_WINDOW_040_VOLMA_010_HOLD_999_RSI-HI... 98.340 None
11 Model_028_WINDOW_040_VOLMA_010_HOLD_999_RSI-HI... 98.340 None
# Calculate the stock's performance for a buy-and-hold model
top_model_name = model_performance_summary.loc[0]['Model_name']
top_trading_model = trading_model_collection[top_model_name]
print('The entry point for the buy-and-hold model: $%.2f on %s' % (top_trading_model.iloc[0]['open_price'], top_trading_model.index[0].date()))
print('The exit point for the buy-and-hold model: $%.2f on %s' % (top_trading_model.iloc[-1]['open_price'], top_trading_model.index[-1].date()))
print('The performance of the buy-and-hold model: $%.2f' %(top_trading_model.iloc[-1]['open_price'] - top_trading_model.iloc[0]['open_price']))
print('The performance of the top trading model: $%.2f' %(model_performance_summary.iloc[0]['Return_value']))
The entry point for the buy-and-hold model: $159.81 on 2016-01-04 The exit point for the buy-and-hold model: $371.26 on 2021-04-23 The performance of the buy-and-hold model: $211.45 The performance of the top trading model: $143.52
top_model_positions = model_positions_collection[top_model_name]
print(top_model_positions[top_model_positions['trade_action'] != 0])
trade_action price_executed qty_transacted cost_basis gain_loss qty_on_hand cash_on_hand position_value total_position accum_return date 2016-01-08 True 155.28 1 -155.28 0 1 -155.28 135.069 -20.211 -20.211 2016-02-22 True 149.63 -1 0 -5.65 0 -5.65 0 -5.65 -5.65 2016-04-15 True 151.96 1 -151.96 0 1 -157.61 136.378 -21.232 -21.232 2016-06-02 True 150.05 -1 0 -1.91 0 -7.56 0 -7.56 -7.56 2016-08-26 True 165.39 1 -165.39 0 1 -172.95 146.782 -26.168 -26.168 2016-11-18 True 150.84 -1 0 -14.55 0 -22.11 0 -22.11 -22.11 2017-01-05 True 160.95 1 -160.95 0 1 -183.06 146.318 -36.742 -36.742 2017-01-18 True 163.65 -1 0 2.7 0 -19.41 0 -19.41 -19.41 2017-03-06 True 169.41 1 -169.41 0 1 -188.82 150.149 -38.671 -38.671 2017-04-10 True 170.2 -1 0 0.79 0 -18.62 0 -18.62 -18.62 2017-06-19 True 167.05 1 -167.05 0 1 -185.67 154.447 -31.223 -31.223 2017-08-01 True 159.1 -1 0 -7.95 0 -26.57 0 -26.57 -26.57 2018-01-03 True 188.82 1 -188.82 0 1 -215.39 180.193 -35.197 -35.197 2018-01-23 True 193.61 -1 0 4.79 0 -21.78 0 -21.78 -21.78 2018-02-06 True 178.91 1 -178.91 0 1 -200.69 174.927 -25.763 -25.763 2018-02-26 True 190.1 -1 0 11.19 0 -10.59 0 -10.59 -10.59 2018-10-08 True 219.34 1 -219.34 0 1 -229.93 213.423 -16.507 -16.507 2018-10-22 True 229.88 -1 0 10.54 0 -0.05 0 -0.05 -0.05 2018-11-21 True 220.56 1 -220.56 0 1 -220.61 208.812 -11.798 -11.798 2018-12-07 True 233.15 -1 0 12.59 0 12.54 0 12.54 12.54 2018-12-17 True 206.68 1 -206.68 0 1 -194.14 193.202 -0.938 -0.938 2019-01-09 True 209.15 -1 0 2.47 0 15.01 0 15.01 15.01 2019-06-03 True 239.78 1 -239.78 0 1 -224.77 231.552 6.782 6.782 2019-06-13 True 257.71 -1 0 17.93 0 32.94 0 32.94 32.94 2019-08-01 True 275.75 1 -275.75 0 1 -242.81 263.729 20.919 20.919 2019-08-29 True 297.48 -1 0 21.73 0 54.67 0 54.67 54.67 2019-09-23 True 286.36 1 -286.36 0 1 -231.69 276.822 45.132 45.132 2019-10-14 True 298 -1 0 11.64 0 66.31 0 66.31 66.31 2019-12-03 True 295.13 1 -295.13 0 1 -228.82 285.241 56.421 56.421 2020-01-17 True 303.4 -1 0 8.27 0 74.58 0 74.58 74.58 2020-02-05 True 307 1 -307 0 1 -232.42 298.053 65.633 65.633 2020-02-18 True 319 -1 0 12 0 86.58 0 86.58 86.58 2020-02-28 True 285.25 1 -285.25 0 1 -198.67 271.792 73.122 73.122 2020-04-16 True 314.73 -1 0 29.48 0 116.06 0 116.06 116.06 2020-06-15 True 297.05 1 -297.05 0 1 -180.99 287.962 106.972 106.972 2020-07-07 True 310.02 -1 0 12.97 0 129.03 0 129.03 129.03 2020-09-18 True 340.2 1 -340.2 0 1 -211.17 326.241 115.071 115.071 2020-10-02 True 354.728 -1 0 14.528 0 143.558 0 143.558 143.558 2020-10-29 True 363.23 1 -363.23 0 1 -219.672 354.984 135.312 135.312 2020-11-24 True 383.69 -1 0 20.46 0 164.018 0 164.018 164.018 2021-01-20 True 354.39 1 -354.39 0 1 -190.372 360.591 170.219 170.219 2021-03-23 True 333.89 -1 0 -20.5 0 143.518 0 143.518 143.518
draw_model_graph(trading_model_collection[top_model_name], top_model_name)
print ('Total time for the script:',(datetime.now() - startTimeScript))
Total time for the script: 0:04:00.280293